"""
Copyright © 2021 Walkline Wang (https://walkline.wang)
Gitee: https://gitee.com/walkline/iot-explorer-sdk-for-micropython
"""
import socket
import json
import utime
import urequests

from utils.wifihandler import WifiHandler
from utils.utilities import Utilities
from services.mqtt_service import MQTTService
from utils.codec import Codec


DEVICE_NAME = 'dynClock02'
DEVICE_SECRET = 'QLbAYsam+1jF4dfsKG3JJg=='
PRODUCT_ID = 'WAWI543E4J'
PRODUCT_SECRET = '0Ke5nnFasiXtLwuejqcFX86Z'
MQTT_HOST = f'{PRODUCT_ID}.iotcloud.tencentdevices.com'
DYNAMIC_REGISTER_HOST = 'http://ap-guangzhou.gateway.tencentdevices.com/register/dev'

# 物模型Topic
# UP 为 发布，DOWN 为 订阅
PROPERTY_UP = f'$thing/up/property/{PRODUCT_ID}/${{{DEVICE_NAME}}}'
PROPERTY_DOWN = f'$thing/down/property/{PRODUCT_ID}/${{{DEVICE_NAME}}}'
EVENT_UP = f'$thing/up/event/{PRODUCT_ID}/${{{DEVICE_NAME}}}'
EVENT_DOWN = f'$thing/down/event/{PRODUCT_ID}/${{{DEVICE_NAME}}}'
ACTION_UP = f'$thing/up/action/{PRODUCT_ID}/${{{DEVICE_NAME}}}'
ACTION_DOWN = f'$thing/down/action/{PRODUCT_ID}/${{{DEVICE_NAME}}}'


def start_smartconfig() -> bool:
	return WifiHandler.STATION_CONNECTED == WifiHandler.set_sta_mode()

def start_udp_server() -> str:
	port = 8266
	udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	udp.bind(('0.0.0.0', port))
	
	result, client_addr = udp.recvfrom(1024)

	print(f'received from {client_addr}')
	result_json = json.loads(result)
	print(f"- cmdType: {result_json['cmdType']}\n- token: {result_json['token']}")

	reply = {
		'cmdType': 2,
		'productId': PRODUCT_ID,
		'deviceName': DEVICE_NAME,
		'protoVersion': '0.1'
	}

	udp.sendto(json.dumps(reply), client_addr)

	return result_json['token']

def start_mqtt_client(token) -> bool:
	client_id = PRODUCT_ID + DEVICE_NAME
	device_secret = __dynamic_register_device() if PRODUCT_SECRET else DEVICE_SECRET
	username, password = __generate_mqtt_password(client_id, device_secret)

	print(f'mqtt info:\n- client id: {client_id}\n- host: {MQTT_HOST}\n- username: {username}\n- password: {password}')
	client = MQTTService(
		client_id,
		MQTT_HOST,
		1883,
		username,
		password
	)

	client.set_callback(__mqtt_callback)
	client.connect()
	client.subscribe(f'$thing/down/service/{PRODUCT_ID}/{DEVICE_NAME}')

	publish = {
		'method': 'app_bind_token',
		'clientToken': f'{DEVICE_NAME};token',
		'params': {
			'token': token
		}
	}

	client.publish(f'$thing/up/service/{PRODUCT_ID}/{DEVICE_NAME}', json.dumps(publish))

def __mqtt_callback(topic, msg):
	print(f'topic: {topic}\nmessage: {msg}')

def __generate_mqtt_password(client_id:str, device_secret:str=None) -> tuple:
	psk = Codec.BASE64.decode(device_secret)
	timestamp = (utime.time() + WifiHandler.INTERVAL_FROM_1970_TO_2000) * 1000
	conn_id = Utilities.get_random_sample(5)
	username = f'{client_id};21010406;{conn_id};{timestamp}'
	sign = Codec.BASE64.hexlify(Codec.HMAC.sha1(psk, username))
	password = f"{sign.decode('utf-8')};hmacsha1"

	print(
f'''
generate mqtt password:
- psk: {psk}
- timestamp: {timestamp}
- conn_id: {conn_id}
- username: {username}
- sign: {sign}
- password: {password}
'''
		)

	return username, password

def __dynamic_register_device():
	device_name = DEVICE_NAME
	nonce = Utilities.get_random_nonce()
	product_id = PRODUCT_ID
	timestamp = int((utime.time() + WifiHandler.INTERVAL_FROM_1970_TO_2000) * 1000)
	sign_string = f'deviceName={device_name}&nonce={nonce}&productId={product_id}&timestamp={timestamp}'
	sign = Codec.BASE64.hexlify(Codec.HMAC.sha1(PRODUCT_SECRET, sign_string))
	sign = Codec.BASE64.encode(sign)

	params = {
		'productId': product_id,
		'deviceName': device_name,
		'nonce': nonce,
		'timestamp': timestamp,
		'signature': sign
	}

	response = urequests.post(DYNAMIC_REGISTER_HOST, json=params)
	print(response.status_code, response.reason)
	print(f'response: {response.content}')

	response = response.json()

	if response['code'] == 0:
		payload_bytes = Codec.AES.decrypt(response['payload'], PRODUCT_SECRET[:16], Codec.AES.MODE_CBC, '0' * 16)

		if payload_bytes:
			try:
				payload = json.loads(payload_bytes.decode('utf-8', 'unicode_escape'))
				print(f'payload: {payload}')

				return payload['psk']
			except UnicodeError:
				print('decrypt failed')
	
	return False


if __name__ == '__main__':
	sc_result = start_smartconfig()

	if sc_result:
		token = start_udp_server()

		if token:
			WifiHandler.sync_time()
			start_mqtt_client(token)
